Skip to content
main
Switch branches/tags
jam0001/dank/
jam0001/dank/

Latest commit

* project scaffold

* wasm support

* start the parser

* fix the tests (now one fails)

* parse literals

* add binary and unary operators

* add object literals

* parse code inside header comments

* implement blocks and while loops

* add a todo

* env and eval prototype

* remove header comments

* fix comment parsing; implement some of the evaluator functionality

* property accesses and function calls

* function declarations and basic comment evaluation

* expression statements

* add single-quoted strings

* binary and unary operators

* __call__ support

* control flow signals

* comparison operator and grammar fixes

* add if statements, add comparison operators, move binops into rust traits, add input()

* comment-time execution fix

* property assignments

* add native object trait

* implementing AST access API

* allow whitespace in function names; add please/do syntax for claling functions

* AST API POC

* more AST API additions

* move Block into its own node to make AST replacements easier

* support getting and updating most expressions

* add a flag for printing the ast

* quick object literal parser fix (allow newlines)

* dynamic properties, block AST modifications

* if statement AST support

* readme and build instructions

* readme fix

* slightly improve main parser errors

* move the code out of the nested directory
1bb2ce1

Git stats

Files

Permalink
Failed to load latest commit information.

Liner

Liner is a simple programming language where comments enable metaprogramming. In Liner, any comment that contains valid Liner code can modify the program by manipulating the AST or outputting code.

Build instructions

  1. Obtain a nightly Rust compiler using rustup.rs. Note: if your default rustc is stable, prefix each cargo command with +nightly or switch to nightly by default.
  2. Build the project and run the tests with cargo test.
  3. Read the next section and run the sample programs using cargo run --release -- <file.ln>. For example, to run readme.lm, execute:
$ cargo run --release -- examples/readme.ln
x is less than y
{ 7: "seven", bool: true, cmp: <fn: lambda@310:475>, nil: null, number: 42, string: "a string" }
5
9
  1. (optional) Alternatively, compile with debug assertion to see comment parsing errors:
$ cargo run -- examples/readme.ln
[Parser] Failed to evaluate a comment: error at 2:19: expected one of "!=", "&&", "*", "+", "-", "/", ";", "<", "<=", "=", "==", ">", ">=", "[", "||", [' ' | '\t' | '\n' | '\r'], [' ' | '\t']
<--snip-->
  1. (optional) To display the initial AST of the program, use the -a flag:
$ cargo run -- examples/readme.ln -a
Ast
╰─statements=↓
  ├─LineComment
  │ ├─body: CommentBody::Text
  │ │ ╰─text: "Liner has objects (essentially hashmaps), functions (named and anonymous), numbers, strings, booleans, and null.\nIn terms of control flow, there are if-else, while, break/continue, and return statements."
  │ ╰─stmt: StmtKind::LetDecl
  │   ├─name: "object"
  │   ╰─initializer: ExprKind::ObjectLiteral
  │     ╰─field0=↓
...
<--snip-->

Syntax and Semantics

Liner's syntax is very much Rust-inspired while its semantics closely resemble Lua, although with a few minor differences. The following snippet demonstrates the available data types and syntactic constructs:

// File: examples/readme.ln

// Liner has objects (essentially hashmaps), functions (named and anonymous), numbers, strings, booleans, and null.
// In terms of control flow, there are if-else, while, break/continue, and return statements.
let object = {
    number: 42,
    string: 'a' + " string",
    bool: true,
    nil: null,
    cmp: fn (x, y) {
        if x == y { return "x is the same as y"; }
        else if x < y { return "x is less than y"; }
        else { return "y is less than x"; }
    }
};
// Assign dynamic properties with the bracket syntax
object[7] = "seven";
// And access fields with the dot syntax
print object.cmp(2, 3);

// Almost everything can be pretty-printed.
print object;

while object.number < 50 {
    object.number = object.number + 1;
    break;
}

// Functions can contain spaces in their names
fn display sum(x, y) { print x + y; }

// and be called either normally
display sum(3, 2);

// or with the `please` and `do` statements
please display sum of 4, 5.

First-class comments

Comments in Liner are meant to do metaprogramming. A Liner comment may modify the program in two different ways:

  1. By printing code inside a comment.
  2. By directly modifying the AST object of the next statement.

Let's look at both ways:

// File: examples/comment-time-execution.ln

// Note: At the moment, the entire comment group must consist of valid Liner code, so
// double comments have to be employed if additional annotations are desired.

// // Way #1: print the desired code
// // The comment below will cause the `null;` statement to be replaced with a print.
// print "print 'Hello, World!';";
null;

// // Way #2: modify the AST
// AST.__stmt__ = "print 'changing the AST';";
null;

// A function defined during comment-time execution will be available to all following comments,
// but not the runtime objects:

// fn display ast() {
//     dbg(AST.tree);
// }

print "runtime output";

// // Now we can use this function to print a statement's AST like this:
// please display ast
let tau = 3.151592 * 2;
print tau; // this statement will not be affected

// // It is possible to selectively modify parts of the ast:
// AST.initializer.left.name = "tau";
let pi = X / 2;

print pi;


// Printing the AST at comment-time was neat, but what if we want to do this at runtime?
// Easy, we just need to insert it into the output stream, while preserving the existing content:

// fn display ast at runtime () {
//     AST.__stmt__ = fmt("{ print '{}';\n {} }", AST.tree, AST.code);
//     AST.unscoped = true; // this disables the block scoping introduced on the previous line
// }

// please display ast at runtime
let receipt = { subtotal: 146.6, tax: 0.13 };
print receipt;